using System;
using System.Xml;
using System.Reflection;
using System.Collections.Generic;

using Elephant.Framework.XML;

namespace Elephant.Framework
{
    internal interface IExecuteable
    {
        void Execute();
    }

    internal abstract class MemberExecuter : IExecuteable
    {
        protected MemberInfo member;
        protected object obj, value;

        public MemberExecuter(MemberInfo member, object obj, object value)
        {
            this.member = member;
            this.obj = obj;
            this.value = value;
        }

        public object Value
        {
            get
            {
                return value;
            }
            set
            {
                this.value = value; // heh heh
            }
        }

        public abstract Type MemberType
        {
            get;
        }

        #region IExecuteable Members

        public abstract void Execute();

        #endregion
    }

    internal class FieldSetExecuter : MemberExecuter
    {
        public FieldSetExecuter(FieldInfo member, object obj, object value)
            : base(member, obj, value) { }

        public override Type MemberType
        {
            get
            {
                return ((FieldInfo)member).FieldType;
            }
        }

        public override void Execute()
        {
            ((FieldInfo)member).SetValue(obj, value);
        }
    }

    internal class PropertySetExecuter : MemberExecuter
    {
        public PropertySetExecuter(PropertyInfo member, object obj, object value)
            : base(member, obj, value) { }

        public override Type MemberType
        {
            get 
            {
                return ((PropertyInfo)member).PropertyType;
            }
        }

        public override void Execute()
        {
            ((PropertyInfo)member).SetValue(obj, value, null);
        }
    }

    /// <summary>
    /// Represents a factory that will instantiate entity objects, and automatically add them to the assigned manager.
    /// </summary>
    public sealed class EntityFactory : Factory
    {
        ComponentManager manager;

        public EntityFactory(ComponentManager manager)
        {
            this.manager = manager;
        }

        /// <summary>
        /// Creates an entity with the specified name.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="name"></param>
        /// <returns></returns>
        public T Create<T>(string name) where T : Entity
        {
            // if we didn't pass an object array, we would enter an infinite recursive loop
            // since we would essentially be calling this method, which then calls this method.. etc.
            // where we actually wanted to call the method overload taking an object[] parameter list
            return Create<T>(new object[] { name });
        }
        
        /// <summary>
        /// Creates an entity with the specified name, and components.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="name"></param>
        /// <param name="components"></param>
        /// <returns></returns>
        public T Create<T>(string name, params EntityComponent[] components) where T : Entity
        {
            return Create<T>(new object[] { name, components });
        }

        // all the code inside this method could be replaced by 1 line, if the Compact Framework hadn't stripped System.Activator down to a bare minimum
        /// <summary>
        /// Creates an entity with the specified parameters.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="parameters"></param>
        /// <returns></returns>
        public T Create<T>(params object[] parameters) where T : Entity
        {
            ConstructorInfo bestMatch = GetBestMatchingConstructor(typeof(T), parameters);

            object o = null;

            try {
                o = bestMatch.Invoke(parameters);
            } catch (TargetInvocationException) {
                throw;
            } catch (TargetParameterCountException) {
                throw;
            }

            Entity entity = o as Entity;

            entity.Manager = manager;
            manager.Add(entity);

            return o as T;
        }

        public Entity CreateFrom(string templateFilename)
        {
            EntityTemplate template = EntityTemplateReader.ReadFrom(templateFilename);
            Entity entity = null;

            if (template != null) {
                entity = Create<Entity>(template.Name);
            }

            foreach (ComponentTemplate componentTemplate in template.ComponentTemplates) {
                // todo: get parameters to pass
                
                EntityComponent component = null;

                // todo: instead playing the try-catch game, we could implement a .Contains(Type type) in componentcollection
                try {
                    component = entity[Type.GetType(componentTemplate.Type, true, true)];
                } catch (KeyNotFoundException) {
                    component = ComponentFactory.Create(
                        Type.GetType(componentTemplate.Type, true, true),
                        entity,
                        false);
                }

                foreach (string path in componentTemplate.Defaults.Keys) {
                    // make a new list to hold field/property executers
                    List<MemberExecuter> executables = new List<MemberExecuter>();

                    // split the path up, so we can loop over it easily
                    string[] pathSplit = path.Split('.');

                    // we need to maintain a variable that will point to the current object each iteration (current
                    // object meaning the object that contains the field/property we're currently at in the path).
                    // kick it off with the component obj, as this is the object we'll find the first property/field in
                    object currentObject = component;

                    // start looping over the path
                    for (int i = 0; i < pathSplit.Length; i++) {
                        // check if we're past the very first item
                        if (i > 0) {
                            // if so, we need to set the previous executers' value to the current object
                            executables[i - 1].Value = currentObject;
                        } 

                        string memberName = pathSplit[i];

                        // todo: following returns an array, currently we just use the result from index0 - this is a temporary solution
                        MemberInfo[] members = currentObject.GetType().GetMember(memberName);
                        MemberInfo member = null;

                        if (members.Length > 0) {
                            // assuming the first result is what we want :D
                            member = members[0];
                        }

                        object value = null;

                        // check if we're at the last item
                        if (i == pathSplit.Length - 1) {
                            // if so, we want to convert the value from string (as read from xml) to the proper type
                            // the proper type is known from the member
                            Type targetType = 
                                member is PropertyInfo ? 
                                ((PropertyInfo)member).PropertyType : 
                                ((FieldInfo)member).FieldType;

                            // converting is thankfully easily achieved
                            value = Convert.ChangeType(
                                componentTemplate.Defaults[path], // the value
                                targetType,
                                new System.Globalization.CultureInfo("en-US"));
                        }

                        if (member is PropertyInfo) {
                            executables.Add(new PropertySetExecuter(
                                (PropertyInfo)member,
                                currentObject,
                                value));

                            // update current object to point to the members reference
                            currentObject = ((PropertyInfo)member).GetValue(currentObject, null);
                        } else if (member is FieldInfo) {
                            executables.Add(new FieldSetExecuter(
                                (FieldInfo)member,
                                currentObject,
                                value));

                            // update current object to point to the members reference
                            currentObject = ((FieldInfo)member).GetValue(currentObject);
                        }
                    }

                    // looping backwards 'cause we're setting a value on the end of the tree, then updating previous references as we go
                    // had we started from 0, we would essentially have no change except the end (this is alrighty for reference types, but not for value types)
                    for (int j = executables.Count - 1; j >= 0; j--) {
                        executables[j].Execute();
                    }
                }
            }

            return entity;
        }
    }
}
